package parse

import (
	"fmt"
	"strings"

	"github.com/dave/dst"
)

func GetExportPath(pkgPath string) string {
	return strings.ReplaceAll(pkgPath, "/", "_")
}

type TypeInfo struct {
	NameSpace   string
	TypeName    string
	IsInterface bool
	IsMapType   bool
	KeyType     *TypeInfo
	ValueType   *TypeInfo
	IsArray     bool
}

func NewTypeInfo(namespace string, typename string, isInterface bool, isMap bool, isArray bool) *TypeInfo {
	return &TypeInfo{
		NameSpace:   namespace,
		TypeName:    typename,
		IsInterface: isInterface,
		IsMapType:   isMap,
		IsArray:     isArray,
	}
}
func IsStartType(exp dst.Expr) bool {
	_, ok := exp.(*dst.StarExpr)
	return ok
}
func IsArrayType(exp dst.Expr) bool {
	typeInfo := GetFieldTypeName(exp)
	return typeInfo.IsArray
}

func IsBasicType(expr dst.Expr) bool {
	typeInfo := GetFieldTypeName(expr)
	if typeInfo.IsMapType {
		return false
	}
	if typeInfo.IsInterface {
		return false
	}
	if typeInfo.IsArray {
		return false
	}
	_, ok := BaseGoTypeNames[typeInfo.TypeName]
	if ok {
		return true
	}
	return false
}

func GetFieldTypeName(exp dst.Expr) *TypeInfo {
	switch realField := exp.(type) {
	case *dst.SelectorExpr:
		refClassName := realField.Sel.Name
		refPkgName := realField.X.(*dst.Ident).Name
		return NewTypeInfo(refPkgName, refClassName, false, false, false)
	case *dst.Ident:
		return NewTypeInfo("", realField.Name, false, false, false)
	case *dst.StarExpr:
		switch fieldRef := realField.X.(type) {
		case *dst.SelectorExpr:
			return NewTypeInfo(fieldRef.X.(*dst.Ident).Name, fieldRef.Sel.Name, false, false, false)
		case *dst.Ident:
			return NewTypeInfo("", fieldRef.Name, false, false, false)
		}
	case *dst.InterfaceType:
		return NewTypeInfo("", "interface{}", true, false, false)
	case *dst.MapType:
		keyName := GetFieldTypeName(realField.Key)
		valueName := GetFieldTypeName(realField.Value)
		info := NewTypeInfo("", "map", false, true, false)
		info.KeyType = keyName
		info.ValueType = valueName
		return info
	case *dst.Ellipsis:
		elementType := GetFieldTypeName(realField.Elt)
		info := NewTypeInfo("", "array", false, false, true)
		info.ValueType = elementType
		return info
	case *dst.ArrayType:
		elementType := GetFieldTypeName(realField.Elt)
		info := NewTypeInfo("", "array", false, false, true)
		info.ValueType = elementType
		return info
	}
	panic("not supported")
}

var BaseGoTypeNames = map[string]interface{}{
	"int":     nil,
	"int32":   nil,
	"int64":   nil,
	"uint":    nil,
	"uint32":  nil,
	"uint64":  nil,
	"float32": nil,
	"float64": nil,
	"bool":    nil,
}

func GetMappedFieldTypeName(info *TypeInfo) *TypeInfo {
	//if !info.IsMapType && !info.IsInterface && info.NameSpace == "" {
	//	_, isBasic := BaseGoTypeNames[info.TypeName]
	//	if isBasic {
	//		return NewTypeInfo("go", info.TypeName, false, false)
	//	}
	//}
	if info.IsMapType {
		return &TypeInfo{
			NameSpace:   "",
			TypeName:    "GoMap",
			IsInterface: false,
			IsMapType:   false,
			//KeyType:     GetMappedFieldTypeName(info.KeyType),
			//ValueType:   GetMappedFieldTypeName(info.ValueType),
		}
	}
	if info.TypeName == "string" {
		return NewTypeInfo("", "GoString", false, false, false)
	}
	if info.IsInterface {
		return NewTypeInfo("", "WASMObj", false, false, false)
	}
	return info
}

func FormatName(info *TypeInfo) string {
	if info.IsArray {
		return FormatName(info.ValueType) + "[]"
	} else {
		if info.NameSpace == "" {
			_, ex := BaseGoTypeNames[info.TypeName]
			if ex {
				return fmt.Sprintf("%s", info.TypeName)
			}
			return info.TypeName
		}
		return fmt.Sprintf("%s.%s", info.NameSpace, info.TypeName)
	}
}

func FuncCallFormat(typeInfo *TypeInfo, argName string) string {
	callBuilder := new(strings.Builder)
	if typeInfo.IsInterface {
		callBuilder.WriteString(fmt.Sprintf("%s.typeCode,", argName))
		callBuilder.WriteString(fmt.Sprintf("%s.ptr,", argName))
	} else if typeInfo.IsMapType {
		callBuilder.WriteString(argName)
		callBuilder.WriteString(",")
	} else if typeInfo.IsArray {
		callBuilder.WriteString(argName)
		callBuilder.WriteString(",")
	} else if typeInfo.TypeName == "GoString" {
		callBuilder.WriteString(fmt.Sprintf("%s.valueOffset,%s.byteLen", argName, argName))
		callBuilder.WriteString(",")
	} else if typeInfo.TypeName == "bool" {
		callBuilder.WriteString(fmt.Sprintf("Number(%s)", argName))
		callBuilder.WriteString(",")
	} else {
		_, ok := BaseGoTypeNames[typeInfo.TypeName]
		if ok {
			callBuilder.WriteString(argName)
			callBuilder.WriteString(",")
		} else {
			callBuilder.WriteString(argName + ".ptr,")
		}
	}
	return callBuilder.String()
}

func FuncCanNotExport(realNode *dst.FuncDecl) bool {
	fname := realNode.Name.Name
	firstChar := fname[0:1]
	if strings.ToUpper(firstChar) != firstChar {
		return true
	}
	if fname[0] < 'A' || fname[0] > 'Z' {
		return true
	}
	canExport := true
	if realNode.Type != nil && realNode.Type.Params != nil {
		for _, field := range realNode.Type.Params.List {
			if IsArrayType(field.Type) {
				canExport = false
				break
			}
			canExport = fieldTypeExported(field)
			if !canExport {
				return true
			}
		}
	}
	retLen := 0
	if realNode.Type != nil && realNode.Type.Results != nil {
		for _, field := range realNode.Type.Results.List {
			if len(field.Names) != 0 {
				retLen += len(field.Names)
			} else {
				retLen += 1
			}
			canExport = fieldTypeExported(field)
			if !canExport {
				return true
			}
		}
	}
	if !canExport {
		return true
	}
	if retLen > 1 {
		return true
	}
	return false
}

func fieldTypeExported(field *dst.Field) bool {
	if !IsBasicType(field.Type) && !IsStartType(field.Type) {
		return false

	}
	if IsArrayType(field.Type) {
		return false

	}
	fieldInfo := GetFieldTypeName(field.Type)
	if fieldInfo.IsMapType && (!(fieldInfo.KeyType.IsInterface && fieldInfo.ValueType.IsInterface)) {
		return false

	}
	return true
}
